﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;

namespace EASM_Language
{
    public class EASM
    {
        #region Public
        public delegate void ReturnEventArgs(int state);
        public static event ReturnEventArgs OnStopExecution;
        #region Runtime Variables, Registers and Labels
        public static int GetLabel(string name)
        {
            if (Labels.ContainsKey(name)) return Labels[name];
            else return -1;
        }
        public static int GetPosition()
        {
            return Line;
        }
        public static object GetVariable(string var)
        {
            if (Variables.ContainsKey(var)) return Variables[var];
            else return "";
        }
        public static object GetRegister(string reg)
        {
            if (Registers.ContainsKey(reg)) return Registers[reg];
            else return "";
        }
        public static string GetAbsoluteValue(string arg)
        {
            string value = arg;
            // In case of it be a variable...
            if (value.StartsWith("{"))
            {
                value = value.Trim('{', '}');
                value = GetVariable(value).ToString();
            }
            // In case of it be a value...
            else if (value.StartsWith("["))
            {
                value = value.Trim('[', ']');
            }
            return value;
        }
        public static string[] GetRegisters()
        {
            List<string> ls = new List<string>();
            foreach (string s in Registers.Keys)
            {
                ls.Add(s);
            }
            return ls.ToArray();
        }
        public static string[] GetVariables()
        {
            List<string> ls = new List<string>();
            foreach (string s in Variables.Keys)
            {
                ls.Add(s);
            }
            return ls.ToArray();
        }
        public static int GetAbsoluteNumber(string arg)
        {
            int num = 0;
            // In case of hex
            if (arg.ToLower().EndsWith("h"))
            {
                num = int.Parse(arg.ToLower().TrimEnd('h'), System.Globalization.NumberStyles.AllowHexSpecifier);
            }
            else
            {
                num = int.Parse(arg);
            }
            return num;
        }
        public static void Clear()
        {
            Variables.Clear();
            Registers.Clear();
            Interrupts.Clear();
            Mnemonics.Clear();
            Labels.Clear();
        }
        public static void EvaluateLine(string line)
        {
            ProcessLine(line);
        }
        public static void RegisterMnemonic(Type t)
        {
            if (t.BaseType == typeof(Mnemonic))
            {
                Mnemonics.Add(CallType(t, "GetName", false, new object[] { }).ToString(), t);
            }
        }
        public static void RegisterInterrupt(Type t)
        {
            if (t.BaseType == typeof(Interrupt))
            {
                Interrupts.Add(int.Parse(CallType(t, "GetCode", false, new object[] { }).ToString()), t);
            }
        }
        public static void SetLabel(string name)
        {
            if (Labels.ContainsKey(name)) Labels[name] = Line;
            else Labels.Add(name, Line);
        }
        public static void SetLabel(string name, int line)
        {
            if (Labels.ContainsKey(name)) Labels[name] = line;
            else Labels.Add(name, line);
        }
        public static void SetVariable(string var, object val)
        {
            if (Variables.ContainsKey(var)) Variables[var] = val;
            else Variables.Add(var, val);
            if (VariableChanged != null) VariableChanged(var, EventArgs.Empty);
        }
        public static void SetRegister(string reg, object val)
        {
            if (Registers.ContainsKey(reg.ToLower())) Registers[reg.ToLower()] = val;
            else Registers.Add(reg.ToLower(), val);
            if (RegisterChanged != null) RegisterChanged(reg, EventArgs.Empty);
        }
        public static void ClearVariable(string var)
        {
            if (Variables.ContainsKey(var)) Variables.Remove(var);
            if (VariableChanged != null) VariableChanged(null, EventArgs.Empty);
        }
        public static void ClearRegister(string reg)
        {
            if (Registers.ContainsKey(reg)) Registers.Remove(reg);
            if (RegisterChanged != null) RegisterChanged(null, EventArgs.Empty);
        }
        public static event EventHandler RegisterChanged;
        public static event EventHandler VariableChanged;
        public static event EventHandler UnknownLine;
        public static event EventHandler DoLine;
        public static event EventHandler Error;
        public static object Parent;
        #endregion
        #region Internal functions
        public static void SetPosition(int line)
        {
            Line = line;
        }
        public static void CallInterrupt(int interrupt)
        {
            if (Interrupts.ContainsKey(interrupt)) CallType(Interrupts[interrupt], "OnCall", false, new object[] { });
            else if (UnknownLine != null) UnknownLine("INT|" + interrupt.ToString(), EventArgs.Empty);
        }
        public static void CallMnemonic(string mnemonic, params object[] args)
        {
            if (Mnemonics.ContainsKey(mnemonic)) CallType(Mnemonics[mnemonic], "OnCall", false, args);
            else if (UnknownLine != null) UnknownLine("MNE|" + mnemonic, EventArgs.Empty);
        }
        public static void StopExecution()
        {
            IsReading = false;
            if (OnStopExecution != null) OnStopExecution(int.Parse(GetRegister("ebx").ToString()));
        }
        public static void GotoLabel(string label)
        {
            if (Labels.ContainsKey(label)) Line = Labels[label];
        }
        public static void Execute(string code)
        {
            PrepareMnemonics();
            PrepareRegisters();
            PrepareInterrupts();
            string[] lines = code.Split('\n');
            string line = "";
            for (; Line < lines.Length; Line++)
            {
                line = lines[Line];
                if (DoLine != null) DoLine(Line, EventArgs.Empty);
                try
                {
                    ProcessLine(line);
                }
                catch (Exception e)
                {
                    if (Error != null) Error(e, EventArgs.Empty);
                    else throw e;
                }
            }
        }
        public static void ExecuteFile(string file)
        {
            string cont = "";
            FileStream fs = null;
            StreamReader sr = null;
            try
            {
                fs = new FileStream(file, FileMode.Open);
                sr = new StreamReader(fs);
                cont = sr.ReadToEnd();
            }
            catch { }
            if (sr != null) sr.Close();
            if (fs != null) fs.Close();
            Execute(cont);
        }
        #endregion
        #endregion
        #region Internal
        internal static int Line;
        internal static bool IsReading;
        internal static Dictionary<string, object> Variables = new Dictionary<string, object>();
        internal static Dictionary<string, object> Registers = new Dictionary<string, object>();
        internal static Dictionary<string, Type> Mnemonics = new Dictionary<string, Type>();
        internal static Dictionary<int, Type> Interrupts = new Dictionary<int, Type>();
        internal static Dictionary<string, int> Labels = new Dictionary<string, int>();
        internal static object CallType(Type t, string f, bool s, params object[] a)
        {
            object r = null;
            if (s)
            {
                MethodInfo m = t.GetMethod(f, BindingFlags.Static);
                r = m.Invoke(null, a);
                if (m.ReturnType == typeof(void)) r = null;
            }
            else
            {
                object o = t.GetConstructor(Type.EmptyTypes).Invoke(new object[] { });
                MethodInfo m = t.GetMethod(f);
                ParameterInfo[] pi = m.GetParameters();
                if (pi.Length > 0) r = m.Invoke(o, new object[] { a });
                else r = m.Invoke(o, a);
                if (m.ReturnType == typeof(void)) r = null;
            }
            return r;
        }
        internal static void PrepareRegisters()
        {
            Registers.Add("eax", "1");
            Registers.Add("ebx", "0");
            Registers.Add("ecx", "");
        }
        internal static void PrepareMnemonics()
        {
            RegisterMnemonic(typeof(MOV));
            RegisterMnemonic(typeof(INT));
            RegisterMnemonic(typeof(DEF));
            RegisterMnemonic(typeof(DEL));
            RegisterMnemonic(typeof(LABEL));
            RegisterMnemonic(typeof(GOTO));
            RegisterMnemonic(typeof(JOIN));
            RegisterMnemonic(typeof(EVAL));
        }
        internal static void PrepareInterrupts()
        {
            EASM.RegisterInterrupt(typeof(I01h));
        }
        internal static string PrepareString(string input)
        {
            return input.Replace("\\n", "\n").Replace("\\t", "\t");
        }
        internal static void ProcessLine(string line)
        {
            line = PrepareString(line);
            if (line.StartsWith(";")) return;
            string mnemonic = line.Split(' ')[0];
            string args = "";
            for (int i = 1; i < line.Split(' ').Length; i++)
            {
                args += line.Split(' ')[i] + " ";
                args.Trim();
            }
            List<object> argx = new List<object>();
            bool isReading = false;
            bool canRead = true;
            bool wasABackSlash = false;
            bool ignore = false;
            int backSlashPos = 0;
            string read = "";
            for (int i = 0; i < args.Length; i++)
            {
                if (canRead)
                {
                    char c = args[i];
                    if (c == '^' && !wasABackSlash) { wasABackSlash = true; backSlashPos = i; ignore = true; }
                    if (i >= backSlashPos + 1) { wasABackSlash = false; ignore = false; }
                    if ((c == '{' || c == '[') && !wasABackSlash) isReading = true;
                    else if ((c == '}' || c == ']') && !wasABackSlash) isReading = false;
                    if (c == ';' && !isReading)
                    {
                        argx.Add(read.TrimEnd('\r', '\n').Trim());
                        read = "";
                        canRead = false;
                    }
                    else
                    {
                        if (c == ',' && !isReading) { argx.Add(read.TrimEnd('\r', '\n').Trim()); read = ""; }
                        else if (!ignore) read += c;
                        if (i == args.Length - 1) argx.Add(read.TrimEnd('\r', '\n').Trim());
                    }
                }
            }
            object[] argxx = argx.ToArray();
            CallMnemonic(mnemonic, argxx);
        }
        #endregion
    }
    public abstract class Interrupt
    {
        public abstract int GetCode();
        public abstract void OnCall();
    }
    public abstract class Mnemonic
    {
        public abstract string GetName();
        public abstract void OnCall(object[] args);
    }
    #region EASM Mnemonics Definition
    public class EVAL : Mnemonic
    {
        public override string GetName()
        {
            return "eval";
        }
        public override void OnCall(params object[] args)
        {
            EASM.EvaluateLine(EASM.GetAbsoluteValue(args[0].ToString()));
        }
    }
    public class MOV : Mnemonic
    {
        public override string GetName()
        {
            return "mov";
        }
        public override void OnCall(params object[] args)
        {
            EASM.SetRegister(args[0].ToString(), EASM.GetAbsoluteValue(args[1].ToString()));
        }
    }
    public class INT : Mnemonic
    {
        public override string GetName()
        {
            return "int";
        }
        public override void OnCall(params object[] args)
        {
            int interrupt = 0;
            string intrr = args[0].ToString();
            // Hexadecimal...
            interrupt = EASM.GetAbsoluteNumber(intrr);
            EASM.CallInterrupt(interrupt);
        }
    }
    public class DEF : Mnemonic
    {
        public override string GetName()
        {
            return "def";
        }
        public override void OnCall(params object[] args)
        {
            EASM.SetVariable(args[0].ToString(), EASM.GetAbsoluteValue(args[1].ToString()));
        }
    }
    public class DEL : Mnemonic
    {
        public override string GetName()
        {
            return "del";
        }
        public override void OnCall(params object[] args)
        {
            EASM.ClearVariable(EASM.GetAbsoluteValue(args[0].ToString()));
        }
    }
    public class LABEL : Mnemonic
    {
        public override string GetName()
        {
            return "label";
        }
        public override void OnCall(object[] args)
        {
            int line = EASM.GetPosition() + 1;
            if (args.Length > 1) line = EASM.GetAbsoluteNumber(args[1].ToString());
            EASM.SetLabel(args[0].ToString(), line);
        }
    }
    public class GOTO : Mnemonic
    {
        public override string GetName()
        {
            return "goto";
        }
        public override void OnCall(object[] args)
        {
            string argx = args[0].ToString();
            int line = 0;
            if (argx.StartsWith("["))
            {
                argx = argx.Trim('[', ']');
                line = EASM.GetLabel(argx);
            }
            else if (argx.StartsWith("{"))
            {
                argx = argx.Trim('{', '}');
                argx = EASM.GetVariable(argx).ToString();
                // The label name
                if (argx.StartsWith("_")) line = EASM.GetLabel(argx.Substring(1));
                // The line number
                else line = EASM.GetAbsoluteNumber(argx);
            }
            else
            {
                line = EASM.GetAbsoluteNumber(argx);
            }
            EASM.SetPosition(line);
        }
    }
    public class JOIN : Mnemonic
    {
        public override string GetName()
        {
            return "join";
        }
        public override void OnCall(object[] args)
        {
            EASM.SetVariable(args[0].ToString(), EASM.GetAbsoluteValue(args[1].ToString()) + EASM.GetAbsoluteValue(args[2].ToString()));
        }
    }
    #endregion
    #region EASM Interrupts Definition
    public class I01h : Interrupt
    {
        public override int GetCode()
        {
            return 0x01;
        }
        public override void OnCall()
        {
            Console.Write(EASM.GetRegister("ecx"));
        }
    }
    #endregion
}